﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SQLite;
using System.Windows.Input;

namespace KeyboardImageViewer
{
    public class InputMapping
    {
        private static Dictionary<UserAction, string> actionFriendlyNames;
        private static Dictionary<Key, string> keyFriendlyNames;
        private static Dictionary<MouseButton, string> mouseButtonFriendlyNames;

        private Dictionary<Key, UserAction> keyMapping;
        private Dictionary<Key, UserAction> keyMappingCtrl;
        private Dictionary<Key, UserAction> keyMappingShift;
        private Dictionary<MouseButton, UserAction> mouseMapping;
        private Dictionary<MouseWheelAction, UserAction> mouseWheelMapping;

        public InputMapping(SQLiteConnection connection)
        {
            this.keyMapping = new Dictionary<Key, UserAction>();
            this.keyMappingCtrl = new Dictionary<Key, UserAction>();
            this.keyMappingShift = new Dictionary<Key, UserAction>();
            this.mouseMapping = new Dictionary<MouseButton, UserAction>();
            this.mouseWheelMapping = new Dictionary<MouseWheelAction, UserAction>();

            SQLiteCommand selectInputMapping = new SQLiteCommand("SELECT * FROM inputMapping", connection);
            using (SQLiteDataReader reader = selectInputMapping.ExecuteReader())
            {
                while (reader.Read())
                {
                    InputType inputType = (InputType)reader.GetInt32("inputType");
                    UserAction action = (UserAction)reader.GetInt32("actionCode");
                    int inputCode = reader.GetInt32("inputCode");

                    switch (inputType)
                    {
                        case InputType.Mouse:
                            this.mouseMapping.Add((MouseButton)inputCode, action);
                            break;
                        case InputType.MouseWheel:
                            this.mouseWheelMapping.Add((MouseWheelAction)inputCode, action);
                            break;
                        case InputType.Keyboard:
                            this.keyMapping.Add((Key)inputCode, action);
                            break;
                        case InputType.KeyboardCtrl:
                            this.keyMappingCtrl.Add((Key)inputCode, action);
                            break;
                        case InputType.KeyboardShift:
                            this.keyMappingShift.Add((Key)inputCode, action);
                            break;
                        default:
                            break;
                    }
                }
            }
        }

        private static Dictionary<UserAction, string> ActionFriendlyNames
        {
            get
            {
                if (actionFriendlyNames == null)
                {
                    actionFriendlyNames = new Dictionary<UserAction, string>();

                    actionFriendlyNames.Add(UserAction.ShowHelp, "Show Help");
                    actionFriendlyNames.Add(UserAction.ShowOptions, "Show Settings");
                    actionFriendlyNames.Add(UserAction.ReloadFilesFromDisk, "Reload Files From Disk");
                    actionFriendlyNames.Add(UserAction.ToggleFullscreen, "Toggle Fullscreen Mode");

                    actionFriendlyNames.Add(UserAction.RateAs1, "Rate Image as a 1");
                    actionFriendlyNames.Add(UserAction.RateAs2, "Rate Image as a 2");
                    actionFriendlyNames.Add(UserAction.RateAs3, "Rate Image as a 3");
                    actionFriendlyNames.Add(UserAction.RateAs4, "Rate Image as a 4");
                    actionFriendlyNames.Add(UserAction.RateAs5, "Rate Image as a 5");
                    actionFriendlyNames.Add(UserAction.ClearRating, "Clear Image Rating");
                    actionFriendlyNames.Add(UserAction.Tag, "Edit Tags");
                    actionFriendlyNames.Add(UserAction.TagEditMultiple, "Edit Tags on All Visible Images");
                    actionFriendlyNames.Add(UserAction.Rename, "Rename File");
                    actionFriendlyNames.Add(UserAction.Move, "Move All Visible Images");
                    actionFriendlyNames.Add(UserAction.EditCurrentFile, "Edit Current Image");
                    actionFriendlyNames.Add(UserAction.DeleteCurrentFile, "Delete Current Image");
                    actionFriendlyNames.Add(UserAction.Import, "Import Visible Images");
                    actionFriendlyNames.Add(UserAction.EditTagDependencies, "Edit Tag Dependencies");

                    actionFriendlyNames.Add(UserAction.ShowPreviousImage, "Show Previous Image");
                    actionFriendlyNames.Add(UserAction.ShowNextImage, "Show Next Image");
                    actionFriendlyNames.Add(UserAction.MoveToFirstImage, "Move To First Image");
                    actionFriendlyNames.Add(UserAction.MoveToLastImage, "Move To Last Image");
                    actionFriendlyNames.Add(UserAction.ShowLists, "Show Image Lists");
                    actionFriendlyNames.Add(UserAction.ChangeOrder, "Change Sort Order");
                    actionFriendlyNames.Add(UserAction.ClearSearch, "Clear Current Search");
                    actionFriendlyNames.Add(UserAction.ShowRating1OrGreater, "Show Only Rating 1+");
                    actionFriendlyNames.Add(UserAction.ShowRating2OrGreater, "Show Only Rating 2+");
                    actionFriendlyNames.Add(UserAction.ShowRating3OrGreater, "Show Only Rating 3+");
                    actionFriendlyNames.Add(UserAction.ShowRating4OrGreater, "Show Only Rating 4+");
                    actionFriendlyNames.Add(UserAction.ShowRating5OrGreater, "Show Only Rating 5");
                    actionFriendlyNames.Add(UserAction.ClearRatingFilter, "Show All Ratings");
                    actionFriendlyNames.Add(UserAction.ShowOnlyUnrated, "Show Only Unrated Images");
                    actionFriendlyNames.Add(UserAction.ShowOnlyUntagged, "Show Only Untagged Images");
                    actionFriendlyNames.Add(UserAction.ShowDuplicates, "Show Duplicates of Current Image");
                    actionFriendlyNames.Add(UserAction.ShowAllDuplicates, "Show All Duplicates");
                }

                return actionFriendlyNames;
            }
        }

        private static Dictionary<Key, string> KeyFriendlyNames
        {
            get
            {
                if (keyFriendlyNames == null)
                {
                    keyFriendlyNames = new Dictionary<Key, string>();

                    keyFriendlyNames.Add(Key.Left, "←");
                    keyFriendlyNames.Add(Key.Right, "→");
                    keyFriendlyNames.Add(Key.Up, "↑");
                    keyFriendlyNames.Add(Key.Down, "↓");

                    keyFriendlyNames.Add(Key.OemTilde, "~");
                    keyFriendlyNames.Add(Key.Back, "Backspace");
                    keyFriendlyNames.Add(Key.OemMinus, "-");
                    keyFriendlyNames.Add(Key.OemPlus, "+");
                    keyFriendlyNames.Add(Key.Return, "Enter");
                    keyFriendlyNames.Add(Key.OemPipe, @"\");
                    keyFriendlyNames.Add(Key.Escape, "Esc");
                    keyFriendlyNames.Add(Key.PageUp, "Page Up");
                    keyFriendlyNames.Add(Key.Next, "Page Down");

                    keyFriendlyNames.Add(Key.Oem4, "[");
                    keyFriendlyNames.Add(Key.Oem6, "]");
                    keyFriendlyNames.Add(Key.OemSemicolon, ";");
                    keyFriendlyNames.Add(Key.Oem7, "'");
                    keyFriendlyNames.Add(Key.OemComma, "<");
                    keyFriendlyNames.Add(Key.OemPeriod, ">");
                    keyFriendlyNames.Add(Key.Oem2, "/");

                    keyFriendlyNames.Add(Key.D0, "0");
                    keyFriendlyNames.Add(Key.D1, "1");
                    keyFriendlyNames.Add(Key.D2, "2");
                    keyFriendlyNames.Add(Key.D3, "3");
                    keyFriendlyNames.Add(Key.D4, "4");
                    keyFriendlyNames.Add(Key.D5, "5");
                    keyFriendlyNames.Add(Key.D6, "6");
                    keyFriendlyNames.Add(Key.D7, "7");
                    keyFriendlyNames.Add(Key.D8, "8");
                    keyFriendlyNames.Add(Key.D9, "9");

                    keyFriendlyNames.Add(Key.NumPad0, "Numpad 0");
                    keyFriendlyNames.Add(Key.NumPad1, "Numpad 1");
                    keyFriendlyNames.Add(Key.NumPad2, "Numpad 2");
                    keyFriendlyNames.Add(Key.NumPad3, "Numpad 3");
                    keyFriendlyNames.Add(Key.NumPad4, "Numpad 4");
                    keyFriendlyNames.Add(Key.NumPad5, "Numpad 5");
                    keyFriendlyNames.Add(Key.NumPad6, "Numpad 6");
                    keyFriendlyNames.Add(Key.NumPad7, "Numpad 7");
                    keyFriendlyNames.Add(Key.NumPad8, "Numpad 8");
                    keyFriendlyNames.Add(Key.NumPad9, "Numpad 9");
                    keyFriendlyNames.Add(Key.Divide, "Numpad /");
                    keyFriendlyNames.Add(Key.Multiply, "Numpad *");
                    keyFriendlyNames.Add(Key.Subtract, "Numpad -");
                    keyFriendlyNames.Add(Key.Add, "Numpad +");
                    keyFriendlyNames.Add(Key.Decimal, "NumPad .");
                }

                return keyFriendlyNames;
            }
        }

        private static Dictionary<MouseButton, string> MouseButtonFriendlyNames
        {
            get
            {
                if (mouseButtonFriendlyNames == null)
                {
                    mouseButtonFriendlyNames = new Dictionary<MouseButton, string>();

                    mouseButtonFriendlyNames.Add(MouseButton.Left, "Mouse 1");
                    mouseButtonFriendlyNames.Add(MouseButton.Right, "Mouse 2");
                    mouseButtonFriendlyNames.Add(MouseButton.Middle, "Mouse 3");
                    mouseButtonFriendlyNames.Add(MouseButton.XButton1, "Mouse 4");
                    mouseButtonFriendlyNames.Add(MouseButton.XButton2, "Mouse 5");
                }

                return mouseButtonFriendlyNames;
            }
        }

        public static bool IsBound(int inputCode, InputType inputType, SQLiteConnection connection)
        {
            int inputTypeInt = (int)inputType;

            SQLiteCommand isBoundCommand = new SQLiteCommand("SELECT COUNT(*) FROM inputMapping WHERE inputType = " + inputTypeInt + " AND inputCode = " + inputCode, connection);
            int numKeys = Convert.ToInt32(isBoundCommand.ExecuteScalar());

            return numKeys > 0;
        }

        public UserAction GetActionFromKey(Key key)
        {
            if (!this.keyMapping.ContainsKey(key))
            {
                return UserAction.None;
            }

            return this.keyMapping[key];
        }

        public UserAction GetActionFromKeyCtrl(Key key)
        {
            if (!this.keyMappingCtrl.ContainsKey(key))
            {
                return UserAction.None;
            }

            return this.keyMappingCtrl[key];
        }

        public UserAction GetActionFromKeyShift(Key key)
        {
            if (!this.keyMappingShift.ContainsKey(key))
            {
                return UserAction.None;
            }

            return this.keyMappingShift[key];
        }

        public UserAction GetActionFromMouseButton(MouseButton mouseButton)
        {
            if (!this.mouseMapping.ContainsKey(mouseButton))
            {
                return UserAction.None;
            }

            return this.mouseMapping[mouseButton];
        }

        public UserAction GetActionFromMouseWheel(MouseWheelAction mouseWheelAction)
        {
            if (!this.mouseWheelMapping.ContainsKey(mouseWheelAction))
            {
                return UserAction.None;
            }

            return this.mouseWheelMapping[mouseWheelAction];
        }

        public static string GetFriendlyName(int inputCode, InputType inputType)
        {
            if (inputType == InputType.Mouse)
            {
                MouseButton mouseButton = ((MouseButton)inputCode);

                if (MouseButtonFriendlyNames.ContainsKey(mouseButton))
                {
                    return MouseButtonFriendlyNames[mouseButton];
                }

                return mouseButton.ToString();
            }

            if (inputType == InputType.MouseWheel)
            {
                MouseWheelAction wheelAction = (MouseWheelAction)inputCode;
                if (wheelAction == MouseWheelAction.Up)
                {
                    return "MWheel Up";
                }

                if (wheelAction == MouseWheelAction.Down)
                {
                    return "MWheel Down";
                }
            }

            if (inputType == InputType.Keyboard || inputType == InputType.KeyboardCtrl || inputType == InputType.KeyboardShift)
            {
                string prefix = "";
                if (inputType == InputType.KeyboardCtrl)
                {
                    prefix = "Ctrl + ";
                }
                else if (inputType == InputType.KeyboardShift)
                {
                    prefix = "Shift + ";
                }

                Key inputKey = (Key)inputCode;

                if (KeyFriendlyNames.ContainsKey(inputKey))
                {
                    return prefix + KeyFriendlyNames[inputKey];
                }

                return prefix + inputKey.ToString();
            }

            System.Diagnostics.Debug.Assert(false, "Invalid input code/type");

            return null;
        }

        public static string GetFriendlyName(UserAction action)
        {
            if (ActionFriendlyNames.ContainsKey(action))
            {
                return ActionFriendlyNames[action];
            }

            return action.ToString();
        }

        public static void ResetToDefaults(SQLiteConnection connection)
        {
            using (SQLiteTransaction transaction = connection.BeginTransaction())
            {
                Database.ExecuteNonQuery("DELETE FROM inputMapping", connection);

                InsertDefaultsIntoMapping(connection, true);

                transaction.Commit();
            }
        }

        /// <summary>
        /// Adds default key->UserAction mappings.
        /// </summary>
        /// <param name="connection">The connection to the database to add them to.</param>
        /// <param name="latestVersion">True to add the latest defaults, false to add defaults at DB version 3.</param>
        public static void InsertDefaultsIntoMapping(SQLiteConnection connection, bool latestVersion)
        {
            MappingAdder adder = new MappingAdder(connection);
            adder.AddMapping(Key.F1, UserAction.ShowHelp);
            adder.AddMapping(Key.F4, UserAction.ShowOptions);
            adder.AddMapping(Key.F5, UserAction.ReloadFilesFromDisk);
            adder.AddMapping(MouseButton.Middle, UserAction.ToggleFullscreen);
            adder.AddMapping(Key.OemPipe, UserAction.Minimize);
            adder.AddMapping(Key.Escape, UserAction.Quit);

            adder.AddMapping(Key.D1, UserAction.RateAs1);
            adder.AddMapping(Key.D2, UserAction.RateAs2);
            adder.AddMapping(Key.D3, UserAction.RateAs3);
            adder.AddMapping(Key.D4, UserAction.RateAs4);
            adder.AddMapping(Key.D5, UserAction.RateAs5);
            adder.AddMapping(Key.OemTilde, UserAction.ClearRating);
            adder.AddMapping(Key.T, UserAction.Tag);
            adder.AddMapping((int)Key.T, InputType.KeyboardCtrl, UserAction.TagEditMultiple);
            adder.AddMapping(Key.R, UserAction.Rename);
            adder.AddMapping(Key.F2, UserAction.Rename);
            adder.AddMapping(Key.M, UserAction.Move);
            
            adder.AddMapping(Key.Delete, UserAction.DeleteCurrentFile);
            adder.AddMapping(Key.I, UserAction.Import);
            adder.AddMapping(Key.P, UserAction.EditTagDependencies);

            adder.AddMapping(Key.Left, UserAction.ShowPreviousImage);
            adder.AddMapping(MouseWheelAction.Up, UserAction.ShowPreviousImage);
            adder.AddMapping(Key.Right, UserAction.ShowNextImage);
            adder.AddMapping(MouseWheelAction.Down, UserAction.ShowNextImage);
            adder.AddMapping(Key.Home, UserAction.MoveToFirstImage);
            adder.AddMapping(Key.End, UserAction.MoveToLastImage);
            adder.AddMapping(Key.L, UserAction.ShowLists);
            adder.AddMapping(Key.O, UserAction.ChangeOrder);
            adder.AddMapping(Key.F3, UserAction.Search);
            adder.AddMapping(Key.OemQuestion, UserAction.Search);
            adder.AddMapping((int)Key.F, InputType.KeyboardCtrl, UserAction.Search);
            adder.AddMapping(Key.Back, UserAction.ClearSearch);
            adder.AddMapping(Key.NumPad0, UserAction.ClearRatingFilter);
            adder.AddMapping(Key.NumPad1, UserAction.ShowRating1OrGreater);
            adder.AddMapping(Key.NumPad2, UserAction.ShowRating2OrGreater);
            adder.AddMapping(Key.NumPad3, UserAction.ShowRating3OrGreater);
            adder.AddMapping(Key.NumPad4, UserAction.ShowRating4OrGreater);
            adder.AddMapping(Key.NumPad5, UserAction.ShowRating5OrGreater);
            adder.AddMapping((int)Key.OemTilde, InputType.KeyboardCtrl, UserAction.ClearRatingFilter);
            adder.AddMapping((int)Key.D1, InputType.KeyboardCtrl, UserAction.ShowRating1OrGreater);
            adder.AddMapping((int)Key.D2, InputType.KeyboardCtrl, UserAction.ShowRating2OrGreater);
            adder.AddMapping((int)Key.D3, InputType.KeyboardCtrl, UserAction.ShowRating3OrGreater);
            adder.AddMapping((int)Key.D4, InputType.KeyboardCtrl, UserAction.ShowRating4OrGreater);
            adder.AddMapping((int)Key.D5, InputType.KeyboardCtrl, UserAction.ShowRating5OrGreater);
            adder.AddMapping(Key.Add, UserAction.ShowOnlyUnrated);
            adder.AddMapping(Key.Subtract, UserAction.ShowOnlyUntagged);
            adder.AddMapping(Key.D, UserAction.ShowDuplicates);

            if (latestVersion)
            {
                adder.AddMapping(Key.E, UserAction.EditCurrentFile);
                adder.AddMapping((int)Key.D, InputType.KeyboardCtrl, UserAction.ShowAllDuplicates);
            }
        }
    }
}
